﻿using System;
using System.Collections.Generic;
using System.IO;

namespace tuimg.element
{
    using tuimg.helpers;
    using tuimg.structs;

    // 栅格, 为填充字体路径
    public class TuimgGrid
    {
        internal byte[][] grid;
        internal byte[] filters_on; // 滤片
        internal byte[] filters_off; // 滤片
        internal int width_byte;
        public TuimgGrid(int width, int height)
        {
            this.Width = width;
            this.Height = height;
            width_byte = calc_width_byte(width);
            grid = new byte[height][];
            for (var i = 0; i < height; i++) grid[i] = new byte[helper.round_up(width_byte)];
            init_filters();
        }
        public TuimgGrid(Stream s, int width, int height)
        {
            this.Width = width;
            this.Height = height;

            width_byte = (width + ((32 - (width & 31))&31)) >> 3;
            grid = new byte[height][];
            for (var i = 0; i < height; i++) grid[i] = new byte[helper.round_up(width_byte)];
            init_filters();

            for (var i = 0; i < height; i++) s.Read(grid[i], 0, width_byte);
        }
        internal void init_filters()
        {
            filters_on = new byte[8];
            filters_on[0] = 0b10000000;
            for (var i = 1; i < 8; i++) filters_on[i] = (byte)(filters_on[i - 1] >> 1);
            filters_off = new byte[8];
            for (var i = 1; i < 8; i++) filters_off[i] = (byte)(~filters_on[i]);
        }
        internal int calc_width_byte(int width)
        {
            return helper.round_up(width / 8f);
        }
        public int Width { get; internal set; }
        public int Height { get; internal set; }
        public void DrawPath(TuimgPath path)
        {
            DrawPath(0, 0, path, 1);
        }
        public void DrawPath(int x, int y, TuimgPath path, decimal scale)
        {
            struct_path_point p0, p1, p2;
            var raw_g = new TuimgGrid(Width, Height);
            var raw_g_path = new TuimgGrid(Width, Height);
            CopyTo(raw_g);
            Clear();
            byte count = 0;
            foreach(var p in path.read_paths())
            {
                p0 = p.p0.Scale(scale).Move(x, y);
                p1 = p.p1.Scale(scale).Move(x, y);

                switch (p.method)
                {
                    case enum_path_method.Move:
                        if (count++ == 0) break;

                        CopyTo(raw_g_path);
                        Fill(0, 0);
                        Reverse();
                        raw_g.Cross(this);
                        raw_g_path.CopyTo(raw_g);
                        Clear();
                        raw_g_path.Clear();
                        break;
                    case enum_path_method.Line:
                        DrawLine(p0.x, p0.y, p1.x, p1.y);
                        break; 
                    case enum_path_method.Quadratic_Bezier_Curve:
                        p2 = p.p2.Scale(scale).Move(x, y);
                        DrawCurve(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y);
                        break;
                }
            }

            CopyTo(raw_g_path);
            Fill(0, 0);
            Reverse();
            raw_g.Cross(this);
            Clear();
            raw_g.CopyTo(this);
            raw_g_path.CopyTo(this);
        }
        public void DrawSolidRect(int x, int y, int width, int height)
        {
            height += y;
            width += x;
            for (var i = y; i < height; i++) for (var j = x; j < width; j++) On(j, i);
        }
        // 直线
        public void DrawLine(int x1, int y1, int x2, int y2)
        {
            if (x1 == x2 && y1 == y2) return;
            int x, y;
            double t;
            struct_grid_draw_point dp;
            var queue = new Queue<struct_grid_draw_point>();

            queue.Enqueue(new struct_grid_draw_point { t = 1, t2 = 0 });
            while (queue.Count > 0)
            {
                dp = queue.Dequeue();
                t = dp.t2 + (dp.t /= 2d);

                x = (int)((1 - t) * x1 + t * x2);
                y = (int)((1 - t) * y1 + t * y2);
                if (Read(x, y)) continue;
                
                On(x, y);
                queue.Enqueue(new struct_grid_draw_point { t2 = t, t = dp.t });
                queue.Enqueue(dp);
            }
            if (NotClosed(x1, y1)) On(x1, y1);
            if (NotClosed(x2, y2)) On(x2, y2);
        }
        public void DrawCurve(int x1, int y1, int cx1, int cy1, int x2, int y2)
        {
            if (x1 == x2 && y1 == y2) return;
            var queue = new Queue<struct_grid_draw_point>();
            struct_grid_draw_point dp;
            int x, y;
            double t,t2;

            queue.Enqueue(new struct_grid_draw_point { t = 1, t2 = 0});
            while (queue.Count > 0)
            {
                dp = queue.Dequeue();
                t2 = 1 - (t = dp.t2 + (dp.t /= 2d));

                x = (int)(t2 * t2 * x1 + t * (t2 * (cx1<<1) + t * x2));
                y = (int)(t2 * t2 * y1 + t * (t2 * (cy1<<1) + t * y2));
                if (Read(x, y)) continue;

                On(x, y);
                queue.Enqueue(new struct_grid_draw_point { t2 = t, t = dp.t });
                queue.Enqueue(dp);
            }
            if (NotClosed(x1, y1)) On(x1, y1);
            if (NotClosed(x2, y2)) On(x2, y2);
        }
        public bool NotClosed(int x, int y)
        {
            var x1_1 = Read(x - 1, y - 1);
            var x1_2 = Read(x, y - 1);
            var x1_3 = Read(x, y + 1);
            var x2_1 = Read(x, y - 1);
            var x2_2 = Read(x, y);
            var x2_3 = Read(x, y + 1);
            var x3_1 = Read(x+1, y - 1);
            var x3_2 = Read(x+1, y);
            var x3_3 = Read(x+1, y + 1);

            return (x1_2 == x2_2 == x3_2 == false || x1_1 == x2_2 == x3_3 == false || x1_3 == x2_2 == x3_1 == false || x2_1 == x2_2 == x2_3 == false) && !(x1_1 == x1_2 == x1_3 == true || x1_1 == x2_1 == x3_1 == true || x1_3 == x2_3 == x3_3 == false || x3_1 == x3_2 == x3_3 == false);
        }
        // 开启
        public void On(int x, int y)
        {
            grid[y][x>>3] |= filters_on[x&7];
        }
        // 关闭
        public void Off(int x, int y)
        {
            grid[y][x>>3] &= filters_off[x&7];
        }
        public bool Read(int x, int y)
        {
            return (grid[y][x>>3] & filters_on[x&7]) != 0;
        }
        public void Clear()
        {
            for (var i = 0; i < Height; i++) for (var j = 0; j < width_byte; j++) grid[i][j] = 0;
        }
        public void Reverse()
        {
            for (var i = 0; i < Height; i++) for (var j = 0; j < width_byte; j++) grid[i][j] = (byte)~grid[i][j];
        }
        public void Cross(TuimgGrid g)
        {
            for (var i = 0; i < Height; i++) for (var j = 0; j < width_byte; j++) grid[i][j] ^= g.grid[i][j];
        }
        public void And(TuimgGrid g)
        {
            for (var i = 0; i < Height; i++) for (var j = 0; j < width_byte; j++) grid[i][j] |= g.grid[i][j];
        }
        public void BottomCross(TuimgGrid g, int x)
        {
            Cross(g, x, Height - g.Height);
        }
        public void Cross(TuimgGrid g, int x, int y)
        {
            var h = g.Height+y;
            var w = g.Width+x;
            if (h > Height) h = Height;
            if (w > Width) w = Width;
            for (var i = y; i < h; i++) for (var j = x; j < w; j++) if (g.Read(j - x, i - y)) On(j, i);
        }
        public void CopyTo(TuimgGrid g)
        {
            for (var i = 0; i < Height; i++) for (var j = 0; j < width_byte; j++) g.grid[i][j] |= grid[i][j];
        }
        public void CopyTo(TuimgGrid g, int x1, int y1, int x2, int y2)
        {
            for (var i = y1; i < y2; i++) for (var j = x1; j < x2; j++) if (Read(j, i)) g.On(j - x1, i - y1);
        }
        // 删除两侧空白
        public TuimgGrid CopyTrim()
        {
            var empty_left = 0;
            var empty_right = 0;
            var empty_top = 0;
            var empty_bottom = 0;
            int i, j;
            for (i = 0; i < Width; i++)
            {
                for (j = 0; j < Height; j++) if (Read(i, j)) break;
                if (j == Height) empty_left++;
                else break;
            }
            for (i = Width-1; i >=0; i--)
            {
                for (j = 0; j < Height; j++) if (Read(i, j)) break;
                if (j == Height) empty_right++;
                else break;
            }
            if (empty_left + empty_right > Width) return new TuimgGrid(1, 1);
            for (i = 0; i < Height; i++)
            {
                for (j = 0; j < Width; j++) if (Read(j, i)) break;
                if (j == Width) empty_top++;
                else break;
            }
            for (i = Height - 1; i >= 0; i--)
            {
                for (j = 0; j < Width; j++) if (Read(j, i)) break;
                if (j == Width) empty_bottom++;
                else break;
            }

            var w = Width - empty_left - empty_right;
            var h = Height - empty_top - empty_bottom;
            w++;
            h++;
            var g = new TuimgGrid(w, h);
            CopyTo(g, empty_left, empty_top, Width-empty_right, Height-empty_bottom);
            return g;
        }
        public void Save(string path)
        {
            var bmp = new TuimgBmp(Width, Height);
            bmp.DrawGrid(0, 0, this, TuimgColor.Black);
            using (var s = bmp.ToStream())
            using (var fr = File.Create(path)) helper.ReadToMemory(s, fr, (uint)s.Length);
        }
        // 油漆桶
        public void Fill(int x, int y)
        {
            if (Read(x, y)) return;
            // 涟漪
            var queue = new Queue<struct_path_vector_f0>();
            queue.Enqueue(new struct_path_vector_f0 { x = x, y = y, dir= 0xff });


            while (queue.Count > 0)
            foreach(var vector in queue.Dequeue().ReadNexts())
            {
                if (vector.x < 0|| vector.y < 0||vector.x >= Width||vector.y >= Height || Read(vector.x, vector.y)) continue;
                    
                On(vector.x, vector.y);
                queue.Enqueue(vector);
            }
            On(0, 0);
        }

        // 按高度等比缩放
        public TuimgGrid ScaleHeight(int h)
        {
            var per = (double)h/Height;
            var w = helper.round_up(Width * per);

            var x = Width / w;
            var y = Height / h;
            var g = new TuimgGrid(w+1, h+1);

            int i, j, _i, _j;

            for (i = 0, _i =0; i < h; i++, _i+=y)
            {
                for (j = 0,_j = 0; j< w; j++,_j+=x)
                {
                    if (get_scale_value(_j, _i, x, y)) g.On(j, i);
                }
            }
            return g;
        }
        public void Flip_horizontal()
        {
            if (Height < 2) return;
            var buf = new byte[width_byte];
            int i, j, k;
            for (i = 0, j= Height>>1, k = Height-1; j >= 0;j--, i++, k--){
                grid[i].CopyTo(buf, 0);
                grid[k].CopyTo(grid[i], 0);
                buf.CopyTo(grid[k], 0);
            }
        }

        internal bool get_scale_value(int x, int y, int width, int height)
        {
            if (x + 1 >= Width || y + 1 >= Height) return false;
            width +=x;
            height += y;
            if (width > Width) width = Width;
            if (height > Height) height = Height;

            for(var i = y; i<height; i++)
            {
                for (var j = x; j <width; j++)
                {
                    if (Read(j, i))
                        return true;
                }
            }

            return false;

        }
    }
}
